// 日志接口
#pragma once

#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3

#define LOGFILEPATH "serverLog.log"//日志文件路径

const char* log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"};

class log {
public:
    log()
        : _logFd(-1) {}//初始化为-1，表示尚未打开任何文件

    void enable() {
        umask(0);//创建屏蔽字，确保后续创建的文件权限不受当前进程的umask影响

        _logFd = open(LOGFILEPATH, O_WRONLY | O_CREAT | O_APPEND, 0666);//创建日志文件
        assert(_logFd != -1);

        //将_logFd重定向到标准输出STDOUT_FILENO（宏，用于表示标准输出的文件描述符，1）
        //意味着以后所有对标准输出的操作，实际上都会写入_logFd所指向的文件中
        dup2(_logFd, STDOUT_FILENO);//将标准输出和标准错误都重定向到日志文献描述符中
        dup2(_logFd, STDERR_FILENO);
    }

    ~log() {
        if (_logFd != -1) {
            // 该函数确保将与 _logFd相关联的所有修改过的数据
            //（包括元数据）从操作系统缓存同步到存储设备上。
            //即确保了所有缓存的日志数据都被写到了日志文件中。避免丢失。
            fsync(_logFd);
            close(_logFd);
        }
    }

private:
    int _logFd;//日志文件描述符
};


void logMessage(int level, const char* format, ...) {
    assert(level >= DEBUG);
    assert(level <= FATAL);
    char* name = getenv("USER");
    char logInfo[1024];//

    va_list ap;
    va_start(ap, format);
    vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);//安全格式化字符串，防止缓冲区溢出
    va_end(ap); // ap = NULL

    FILE* out = (level == FATAL) ? stderr : stdout;

    time_t tm = time(nullptr);
    struct tm* localTm = localtime(&tm);
    char* localTmStr = asctime(localTm);
    char* nC = strstr(localTmStr, "\n");
    if (nC) {
        *nC = '\0';
    }
    fprintf(out, "%s | %s | %s | %s\n",
            log_level[level],
            localTmStr,
            name == nullptr ? "unknow" : name,
            logInfo);
    //在进行I/O操作时，操作系统和C库通常会使用缓冲机制来提高性能。
    //这意味着当你执行像 fprintf() 或 fwrite() 这样的写操作时，数据首先被写入到一个内存中的缓冲区而不是直接写入到目标文件或设备。
    //只有当缓冲区满了、显式调用了刷新操作（如 fflush()）、程序正常结束或者进行了某些特定的系统调用时，
    //缓冲区的内容才会被实际写入到磁盘或其他输出设备。
    //将c语言中的缓冲区內容完全写到文件中
    // 首先刷新c库的缓冲区到系统缓冲区
    fflush(out);
    fsync(fileno(out));
    //fileno() 返回的是内核分配给这些文件的唯一描述符。
    //使用 fileno(out) 的目的是为了获取当前输出流 (out) 的文件描述符
    // 将系统缓冲区的内容 刷入文件
}
